package com.yangcangclub.commons.velocity;

import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.log.Log;
import org.apache.velocity.runtime.resource.Resource;
import org.apache.velocity.runtime.resource.ResourceCache;
import org.apache.velocity.runtime.resource.ResourceCacheImpl;
import org.apache.velocity.runtime.resource.ResourceFactory;
import org.apache.velocity.runtime.resource.ResourceManager;
import org.apache.velocity.runtime.resource.loader.ResourceLoader;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 *
 * 模板资源管理器加载器
 * 	扩展Velocity ResourceManager
 *
 */
@Component
public class TemplateResourceManager implements ResourceManager {

    /*全局资源缓存 */
    protected ResourceCache globalCache = null;

    /*资源加载器MAP */
    private Map<String, TemplateResourceLoader> resourceLoaderMap = new HashMap<String, TemplateResourceLoader>();

    /*velocity运行期服务 取得Template*/
    protected RuntimeServices runtimeServ = null;

    /*velocity Log*/
    protected Log log = null;

    /**
     * 初始化工作
     */
    public void initialize(final RuntimeServices runtimeServ) {
        this.runtimeServ = runtimeServ;
        log = runtimeServ.getLog();
        if (this.globalCache == null) {
            this.globalCache = new ResourceCacheImpl();
        }
    }

    /**
     * 取得资源
     */
    public synchronized Resource getResource(final String resourceName,
                                             final int resourceType, final String encoding) {
        String resourceKey = resourceType + resourceName;
        //从缓存中取
        Resource resource = globalCache.get(resourceKey);
        //若缓存中没有
        if (resource != null) {
            try {
                //刷新
                refreshResource(resource, encoding);
            } catch (ResourceNotFoundException rnfe) {
                globalCache.remove(resourceKey);
                return getResource(resourceName, resourceType, encoding);
            } catch (ParseErrorException pee) {
                throw pee;
            } catch (RuntimeException re) {
                throw re;
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            //不存在于缓存中 放入缓存
            try {
                resource = loadResource(resourceName, resourceType, encoding);
                if (resource.getResourceLoader().isCachingOn()) {
                    globalCache.put(resourceKey, resource);
                }
            } catch (ResourceNotFoundException rnfe) {
                throw rnfe;
            } catch (ParseErrorException pee) {
                throw pee;
            } catch (RuntimeException re) {
                throw re;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return resource;
    }

    /**
     * 通过资源名称由资源加载器读取资源
     */
    public String getLoaderNameForResource(String resourceName) {
        Iterator<Map.Entry<String, TemplateResourceLoader>> it = this.resourceLoaderMap.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, TemplateResourceLoader> entry = it.next();
            ResourceLoader resourceLoader = entry.getValue();
            InputStream is = null;
            try {
                is = resourceLoader.getResourceStream(resourceName);
                if (is != null) {
                    return entry.getKey();
                }
            } catch (ResourceNotFoundException rnfe) {
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException supressed) {
                    }
                }
            }
        }
        return null;
    }


    //刷新
    protected void refreshResource(final Resource resource,final String encoding)
            throws ResourceNotFoundException,ParseErrorException, Exception {
        if (resource.requiresChecking()) {
            resource.touch();
            if (resource.isSourceModified()) {
                if (!resource.getEncoding().equals(encoding)) {
                    log.warn("Declared encoding for template '"
                            + resource.getName()
                            + "' is different on reload. Old = '"
                            + resource.getEncoding() + "' New = '" + encoding);
                    resource.setEncoding(encoding);
                }
                long howOldItWas = resource.getResourceLoader().getLastModified(resource);
                resource.process();
                resource.setLastModified(howOldItWas);
            }
        }
    }

    //加载资源
    protected Resource loadResource(String resourceName, int resourceType,
                                    String encoding) throws ResourceNotFoundException,
            ParseErrorException, Exception {
        Resource resource = ResourceFactory.getResource(resourceName, resourceType);
        resource.setRuntimeServices(runtimeServ);

        String[] keys = resourceName.split("\\$");
        resource.setName(keys[1]);
        TemplateResourceLoader resourceLoader = this.resourceLoaderMap.get(keys[0]);
        resource.setEncoding(resourceLoader.getTemplateProvider().defaultCharset().name());

        long howOldItWas = 0;
        resource.setResourceLoader(resourceLoader);
        try {
            if (resource.process()) {
                howOldItWas = resourceLoader.getLastModified(resource);
            }
        } catch (ResourceNotFoundException rnfe) {

        }

        if (resource.getData() == null) {
            throw new ResourceNotFoundException("Unable to find resource '"
                    + resourceName + "'");
        }

        resource.setLastModified(howOldItWas);
        resource.setModificationCheckInterval(resource.getResourceLoader()
                .getModificationCheckInterval());
        resource.touch();
        return resource;
    }

    public void setGlobalCache(ResourceCache globalCache) {
        this.globalCache = globalCache;
    }

    public void setResourceLoaderMap(
            Map<String, TemplateResourceLoader> resourceLoaderMap) {
        this.resourceLoaderMap = resourceLoaderMap;
    }
}
